'''
Created on 29/05/2009

@author: eh01
'''
from xml.etree import ElementTree
from yarra.event.eventhandler import BaseEventHandler
from yarra.datastore.dso import DSO
from yarra.datastore.dst import DST
from yarra.datastore.daofactory import DAOFactory

class ParseWSDLHandler(BaseEventHandler):
    '''
    classdocs
    '''

    WSDL_URI = '{http://schemas.xmlsoap.org/wsdl/}'
    XSD_URI = '{http://www.w3.org/2001/XMLSchema}'
        
    def do(self, data):
        
        dso = DSO()
        # The 'start' and 'end' events represent opening and closing element. They are accompanied by the respective Element instance.  By default, only 'end' events are generated
        # The 'start-ns' and 'end-ns' events notify about namespace declarations. They do not come with Elements.
        # Instead, the value of the 'start-ns' event is a tuple (prefix, namespaceURI) that designates the 
        # beginning of a prefix-namespace mapping. The corresponding 'end-ns' event does not have a value (None).
        # It is common practice to use a list as namespace stack and pop the last entry on the 'end-ns' event
        
#        Example of the events
#          <root>
#            <element key='value'>text</element>
#            <element>text</element>tail
#            <empty-element xmlns="http://testns/" />
#          </root>
#        
#          >>> events = ("start", "end", "start-ns", "end-ns")
#          >>> context = etree.iterparse(StringIO(xml), events=events)
#          >>> for action, elem in context:
#          ...     if action in ('start', 'end'):
#          ...         print("%s: %s" % (action, elem.tag))
#          ...     elif action == 'start-ns':
#          ...         print("%s: %s" % (action, elem))
#          ...     else:
#          ...         print(action)
#          start: root
#          start: element
#          end: element
#          start: element
#          end: element
#          start-ns: ('', 'http://testns/')
#          start: {http://testns/}empty-element
#          end: {http://testns/}empty-element
#          end-ns
#          end: root        

        events = "start", "end", "start-ns", "end-ns"
        
        root_element_tree = None
        ns_map = []
        for event, elem in ElementTree.iterparse(data, events):
            if event == "start-ns":
                # add the namespace mapping
                ns_map.append(elem)
            elif event == "end-ns":
                # pop up the last added namespace mapping
                ns_map.pop()
            elif event == "start":
                if root_element_tree is None:
                    root_element_tree = elem
                # add the list of namespace mappings to the element so it has access to it through ns_map variable
                elem.ns_map = dict(ns_map)
            elif event == "end":
                # do nothing
                pass        
        
        # get the target namespace of the wsdl
        target_namespace = root_element_tree.attrib['targetNamespace']
        
        # set the dso uri
        dso.uri = target_namespace
        
        # there can multiple port type nodes
        for port_type_element in root_element_tree.findall('./{0}portType'.format(self.WSDL_URI)):
            # there can be multiple operations per port type
            for operation_element in port_type_element.findall('./{0}operation'.format(self.WSDL_URI)):
#                print 'Operation Name: ' + operation_element.attrib['name']
                
                a_ref = dso.get_next_anonymous_reference()
                dst1 = DST(subject=".", predicate="operation", object=a_ref)
                dst2 = DST(subject=a_ref, predicate="name", literal=operation_element.attrib['name'], literal_type="xs:string")
                
                input_dso = self.get_x_put("input", operation_element, root_element_tree, target_namespace)
                input_dst = DST(subject=a_ref, predicate="input", object=input_dso.uri)
                output_dso = self.get_x_put("output", operation_element, root_element_tree, target_namespace)
                output_dst = DST(subject=a_ref, predicate="output", object=output_dso.uri)
                dso.add_dsts([dst1, dst2, input_dst, output_dst])                

        return dso

    def get_x_put(self, x_put, operation_element, root_element_tree, target_namespace):
        """
        
        """
        # look for the x_put node
        input_element = operation_element.find('./{0}{1}'.format(self.WSDL_URI, x_put))
        
        # get the x_put message name
        target_message_name = get_qualified_string('message', input_element, target_namespace)
        
        # look for all message nodes
        for message_element in root_element_tree.findall('./{0}message'.format(self.WSDL_URI)):
            message_name = get_qualified_string('name', message_element, target_namespace)
            
            # find the target message
            if message_name == target_message_name:
                
                # get the element name of the part
                part_element = message_element.find('./{0}part'.format(self.WSDL_URI))
                target_element_name = get_qualified_string('element', part_element, target_namespace)

                # get the definition for the element
                type_element = root_element_tree.find('./{0}types'.format(self.WSDL_URI))
                schema_element = type_element.find('./{0}schema'.format(self.XSD_URI))
                for element in schema_element.findall('./{0}element'.format(self.XSD_URI)):
                    element_name = get_qualified_string('name', element, target_namespace)
                    if element_name == target_element_name:
                        complex_type_dso = self.get_element_type(element, type_element, target_namespace)
                        return complex_type_dso
        
        raise ParseWSDLException()
                                
    def get_element_type(self, element, type_element, target_namespace):
        element_name = get_qualified_string('name', element, target_namespace)
        # create complex type dso
        dso = None
        dso = DSO()
        dso.uri = element_name
#        print element_name
        for element_node in element.findall('.//{0}element'.format(self.XSD_URI)):
            if self.is_complex_type(element_node):
                complex_type_dso = self.get_complex_type(get_qualified_string('type', element_node, target_namespace), type_element, target_namespace)
                dst = DST(subject=".", predicate="element", object=complex_type_dso.uri)
                dso.add_dsts([dst])
            else:
                dst = DST(subject=".", predicate="element", literal=element_node.attrib['name'], literal_type="xsd:string")
                dso.add_dsts([dst])
#            print 'Element Name: ' + element_node.attrib['name']
        dsodao = DAOFactory().dsodao
        dsodao.save([dso])
        return dso
        
    def get_complex_type(self, target_complex_type_name, type_element, target_namespace):
        '''
        Gets a DSO representation of the complex type (specified by the target_complex_type_name) inside 
        type_element (e.g the <wsdl:types> element).        
        '''
        dso = DSO()
        dso.uri = target_complex_type_name
        for complex_type in type_element.findall('.//{0}complexType'.format(self.XSD_URI)):
            complex_type_name = get_qualified_string('name', complex_type, target_namespace)
            if complex_type_name == target_complex_type_name:
                for element in complex_type.findall('.//{0}element'.format(self.XSD_URI)):
                    if self.is_complex_type(element):
                        complex_type_dso = self.get_complex_type(get_qualified_string('type', element, target_namespace), type_element, target_namespace)
                        dst = DST(subject=".", predicate="element", object=complex_type_dso.uri)
                        dso.add_dsts([dst])
                    else:
                        dst = DST(subject=".", predicate="element", literal=element.attrib['name'], literal_type="xsd:string")
                        dso.add_dsts([dst])
        #            print 'Element Name: ' + element_node.attrib['name']
                dsodao = DAOFactory().dsodao
                dsodao.save([dso])
                return dso
    
    def is_complex_type(self, element):
        attribute_type = element.attrib['type']
        prefix = attribute_type.partition(':')[0]
        if element.ns_map[prefix] == 'http://www.w3.org/2001/XMLSchema':
            return False
        else:
            return True
        
def get_qualified_string(attribute_name, element, target_namespace):
    '''
    returns the attribute value of the specified attribute (i.e attribute_name) of 
    'element' with any namespace prefix replaced by the actual namespace
    '''
    attribute_value = element.attrib[attribute_name]
    if attribute_value.partition(':')[0] == attribute_value:
        return target_namespace + attribute_value
    else:
        return element.ns_map[attribute_value.partition(':')[0]] + attribute_value.partition(':')[2]
    
class ParseWSDLException(Exception):
    pass